/*
 Автор: Швейкин Евгений Юрьевич

 Функциональное описание:     
    Модуль мониторит фифо, а если в нем есть данные, записывает их по амм. 
    Данные могут подаваться с задержкой, запись пачки начнется только после накопления нужного 
кол-ва данных.
    В начале данные пишутся пачками максимальной длины, остаток пачкой нужного размера.
    При получении сигнала начала записи фифо очищается, поэтому заполнить его заранее нельзя.

На вход модуля подается:
 - количество данных, которое необходимо записать, в 32-хбитных словах;
 - адрес памяти куда записывать, в Байтах;
 - запускающий импульс.

*/
//--------------------------------------------------------------------------------------------------
// ********************** МАКРОСЫ ******************************************************************

    // Описывает процесс модуля выделения фронтов сигнала. Строб формируется на том же такте.
    // Так же описывает создание новых сигналов с именем <имя сигнала> + <_rise> (<_fall>, <_edge>)
    `define FRONT(clk, name)                            \
        logic name``_0;                                 \
        logic name``_rise;                              \
        logic name``_fall;                              \
        logic name``_edge;                              \
        assign name``_rise = name & (~name``_0);        \
        assign name``_fall = name``_0 & (~name);        \
        assign name``_edge = name ^ name``_0;           \
        always_ff @(posedge clk)                        \
        begin                                           \
            name``_0 <= name;                           \
        end

    // Описывает процесс пересинхронизации для исключения метастабильности
    `define RESYNC(clk, name, bw)                       \
        logic [bw - 1:0] name``_resync_0``clk;          \
        logic [bw - 1:0] name``_``clk;                  \
        always_ff @(posedge clk)                        \
        begin                                           \
            name``_resync_0``clk <= name;               \
            name``_``clk    <= name``_resync_0``clk;    \
        end

// *********************** НАЧАЛО МОДУЛЯ ***********************************************************
module fifo_to_amm_burst_bw
#(  parameter BURST_SZ      = 32,       // максимальный размер пачки
    parameter FIFO_SZ       = 1024,     // Размер фифо
    parameter BW            = 32        // Ширина шины авалон
)
(
    input  logic                reset_n,
   
    // Управление модулем
    input  logic                clk_ctrl,
    input  logic                start_i,
    input  logic                stop_i,
    input  logic [31:0]         data_size_i,        // Слов
    input  logic [31:0]         base_addr_i,        // Байт
    output logic                busy_o,    
    
    // Порты фифо
    input  logic                clk_fifo,
    input  logic                fifo_wre_i,
    input  logic [BW - 1:0]     fifo_data_i,        // Данные записываемые в фифо
    
    output logic                fifo_wrfull_o,      // Фифо заполнено
    
    output logic                data_req_o,         // Запрос нового отсчета данных
    
    // Сигналы шины master авалон
    input  logic                clk_amm,
    input  logic                avm_waitrequest,

    output logic [31:0]         avm_address,
    output logic                avm_write,
    output logic [9:0]          avm_burstcount,
    output logic                avm_beginbursttransfer,
    output logic[BW - 1:0]      avm_writedata
);
 
//  ********************* КОНСТАНТЫ ****************************************************************
    localparam FIFO_AW       = $clog2(FIFO_SZ);

//  ********************* ФУНКЦИИ ******************************************************************

//  ********************* ТИПЫ ДАННЫХ **************************************************************
    // Состояния автомата считывания пакетов из фифо и передачи их по Avalon
    enum logic [4:0]    {   IDLE_s              = 5'b00001,
                            AMM_REQ_WR_BURST_s  = 5'b00010,
                            AMM_WR_BURST_s      = 5'b00100,
                            CALC_s              = 5'b01000,
                            WAIT_s              = 5'b10000
                        } state_fifo_to_amm_burst, next_state;

    typedef struct packed 
    {   logic [BW - 1:0]            data;
        logic                       wrreq;
        logic [FIFO_AW - 1:0]       wrusedw;
        logic                       wrfull;
        
        logic [BW - 1:0]            q;
        logic                       rdreq;
        logic [FIFO_AW - 1:0]       rdusedw;
        logic                       rdempty;
    } fifo_t;
    
// ********************* ПЕРЕМЕННЫЕ ****************************************************************
    fifo_t                          fifo;
    
    logic                           start_latch;
    logic                           stop_latch;
    logic [31:0]                    base_addr_latch;
    logic [31:0]                    data_size_latch;
    
    logic [9:0]                     burst_word_cnt;
    logic [9:0]                     current_burstcount;
    
    logic                           rdempty;
    logic                           busy;
    
    logic                           end_burst;
    logic                           end_data;
    logic [31:0]                    address;
    logic [31:0]                    addr_step;
    

    logic [31:0]                    data_word_cnt;
    logic                           reset_fifo;
// ********************* ИНИЦИАЛИЗАЦИЯ ПЕРЕМЕННЫХ ДЛЯ СИМУЛЯТОРА ***********************************

    
//  ********************* ASSIGN *******************************************************************
    assign fifo.data        = fifo_data_i;
    assign fifo.wrreq       = fifo_wre_i & (~fifo.wrfull ) & (~stop_latch);
    assign fifo_wrfull_o    = fifo.wrfull;
    // assign data_req_o       = (|data_word_cnt) & (~fifo.wrfull ) & busy;
    assign busy_o           = busy_clk_ctrl;
    assign data_req_o       = (~fifo.wrfull ) & busy_clk_fifo;
    
// ********************* ПРОЦЕССЫ ******************************************************************
    `FRONT (clk_ctrl, start_i)
    
    // Пересинхронизируем для исключения метастабильности
    `RESYNC(clk_amm, data_size_latch, 32)
    `RESYNC(clk_amm, base_addr_latch, 32)
    `RESYNC(clk_amm, busy, 1)
    `RESYNC(clk_amm, start_latch, 1)
    `RESYNC(clk_amm, stop_latch, 1)
    
    `RESYNC(clk_ctrl, busy, 1)
    
    `RESYNC(clk_fifo, busy, 1)
    
    
      // Защелка стартового, стопового сигналов и патаметров записи
    always_ff @(posedge clk_ctrl)
    begin
        // По фронту входного сигнала запуска защелкиваем параметры записи
        if (start_i_rise && (!busy_clk_ctrl)) begin
            start_latch     <= 1'b1;
            data_size_latch <= data_size_i;
            base_addr_latch <= base_addr_i;
            stop_latch      <= 1'b0;
        end
        else if (stop_i)
            stop_latch <= 1'b1;
        else if (busy_clk_ctrl)
            start_latch <= 1'b0;
        else;
    end 
    
    
   // Переключение состояний автомата (классическое описание)
    always_ff @(posedge clk_amm,  negedge reset_n)
    begin
        if (!reset_n) begin
            state_fifo_to_amm_burst   <= IDLE_s;
        end
        else begin
            state_fifo_to_amm_burst   <= next_state; 
        end
    end
    
    // Определение следующего состояния автомата
    always_comb        
    begin
        next_state = state_fifo_to_amm_burst; 
        unique case (state_fifo_to_amm_burst)
            IDLE_s:  
            begin
                if (start_latch_clk_amm && (|data_size_latch_clk_amm))
                    next_state  = WAIT_s;   
                else;
            end     

            WAIT_s:
            begin
                if (stop_latch_clk_amm)
                    next_state  = AMM_REQ_WR_BURST_s;
                else if (data_size_latch_clk_amm >= BURST_SZ) begin
                    if (fifo.rdusedw >= BURST_SZ)
                        next_state  = AMM_REQ_WR_BURST_s;
                    else;
                end
                else begin
                    next_state  = AMM_REQ_WR_BURST_s;
                end
            end
            
            AMM_REQ_WR_BURST_s:
            begin
                // if (!avm_waitrequest)
                    next_state  = AMM_WR_BURST_s;
                // else;
            end
            
            AMM_WR_BURST_s:
            begin
                if (end_burst) begin
                    if (end_data)
                        next_state  = IDLE_s;
                    else
                        next_state  = CALC_s;
                end
                else;
            end
            
            CALC_s:
            begin
                next_state  = WAIT_s;
            end
            
            default: ;
        endcase
    end   

    // Описание выходов автомата 
    always_comb
    begin
        avm_burstcount          = current_burstcount;
        avm_beginbursttransfer  = 1'b0;
        avm_write               = 1'b0;
        avm_address             = address;
        fifo.rdreq              = 1'b0;
        avm_writedata           = fifo.q;
        busy                    = 1'b1;
        reset_fifo              = 1'b0;
        unique case (state_fifo_to_amm_burst)
            IDLE_s:  
            begin
                busy        = 1'b0;
                reset_fifo  = start_i;
            end   
            
            AMM_REQ_WR_BURST_s:     
            begin
                avm_beginbursttransfer  = 1'b1;         // Запрос
                avm_write               = 1'b1;
                if (!avm_waitrequest) begin
                    fifo.rdreq      = 1'b1;
                end
                else;
            end
            
            AMM_WR_BURST_s:     
            begin
                if (!end_burst) begin
                    avm_write           = 1'b1;         // Подтверждение записи
                    if (!avm_waitrequest) begin
                        fifo.rdreq      = 1'b1;
                    end
                    else;
                end
                else;
            end
            
            default: ;
        endcase
    end

    // Счетчик адресов
    always_ff @(posedge clk_amm)
    begin
        unique case (state_fifo_to_amm_burst)
            IDLE_s:
            begin
                if (start_latch_clk_amm) begin
                    data_word_cnt   <= data_size_latch_clk_amm;
                    address         <= base_addr_latch_clk_amm;
                end
                else;
            end
            
            WAIT_s:
            begin
                // Сли получили стоп, проверим сколько данных в фифо. Если больше пачки, отправим 
                // пачку, если меньше все, что в фифо
                if (stop_latch_clk_amm) begin
                    if (fifo.rdusedw >= BURST_SZ)
                        current_burstcount  <= BURST_SZ[9:0];
                    else
                        current_burstcount  <= fifo.rdusedw;
                end
                // Если запросили на передачу данных больше 1 пачки, и если нет стопа
                else if (data_size_latch_clk_amm >= BURST_SZ) begin
                    // Проверяем, накопилось ли в фифо больше 1 пачки
                    if (fifo.rdusedw >= BURST_SZ)
                        current_burstcount  <= BURST_SZ[9:0];
                    else;
                end
                else begin
                    current_burstcount  <= fifo.rdusedw;
                end
            end
            
            AMM_REQ_WR_BURST_s:     
            begin
                end_burst       <= 1'b0;
                burst_word_cnt  <= 10'd0;
                if (avm_write && (!avm_waitrequest)) begin
                    burst_word_cnt  <= 10'd1;
                    data_word_cnt   <= data_word_cnt - 32'd1;
                end
                else;
            end

            AMM_WR_BURST_s:     
            begin
                if (avm_write && (!avm_waitrequest)) begin
                    end_burst       <= 1'b1;
                    burst_word_cnt  <= burst_word_cnt + 10'd1;
                    if (burst_word_cnt < current_burstcount - 1) begin
                        end_burst       <= 1'b0;
                    end
                    else;
              
                    end_data        <= 1'b1;
                    data_word_cnt   <= data_word_cnt - 32'd1;
                    // Пока не переданных данных больше 1 слова, конец данных не ставим
                    // Кроме случая, когда стоит стоп
                    if (data_word_cnt > 32'd1) begin
                        end_data    <= stop_latch_clk_amm;
                    end
                    else;
                end
                else;
            end
                                
            CALC_s:
            begin
                address <= address + (burst_word_cnt << ($clog2(BW / 8)));
            end
            default: ;
        endcase
    end
    
// ********************* ПОДКЛЮЧАЕМЫЕ МОДУЛИ *******************************************************
      // Буфер FIFO
      
    dcfifo  
    #(  
        .lpm_showahead          ("ON"),             // режим ASK выключен
        .lpm_type               ("dcfifo"),         // Одна ширина входа/выхода
        .overflow_checking      ("ON"),             // Проверка переполнения
        .underflow_checking     ("ON"),             // Проверка опустошения
        .use_eab                ("ON"), 
        .rdsync_delaypipe       (4),                
        .wrsync_delaypipe       (4),
        
        .lpm_numwords           (FIFO_SZ),     // Объем фифо
        
        .lpm_width              (BW),             // Разрядность входных данных 
        .lpm_widthu             (FIFO_AW)        // Разрядность адреса входа
    )
    fifo_inst1
    (   
        .aclr                   (~reset_n | reset_fifo),
    
        // Запись в фифо
        .wrclk                  (clk_fifo),    
        .wrreq                  (fifo.wrreq),
        .data                   (fifo.data),
        .wrfull                 (fifo.wrfull),
        .wrusedw                (fifo.wrusedw),
      
        // Чтение из фифо
        .rdclk                  (clk_amm),
        .rdreq                  (fifo.rdreq),
        .q                      (fifo.q),
        .rdusedw                (fifo.rdusedw),
        .rdempty                (fifo.rdempty)
    );
        
endmodule 

// Удаляем макросы, чтобы никому не испортить ничего
`undef FRONT
`undef RESYNC